package org.openstreetmap.josm.plugins.cadastre_es;

import java.util.Vector;
import javax.swing.JOptionPane;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.data.osm.DataSet;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.data.osm.RelationMember;
import org.openstreetmap.josm.data.osm.Way;
import org.openstreetmap.josm.gui.layer.Layer;
import org.openstreetmap.josm.gui.layer.OsmDataLayer;
import org.openstreetmap.josm.plugins.cadastre_es.exceptions.CriticalException;
import org.openstreetmap.josm.plugins.cadastre_es.exceptions.NotClosedPolygon;
import org.openstreetmap.josm.plugins.cadastre_es.exceptions.NotPolygon;

public class Process extends Thread {
	private Vector<Polygon> polygons = new Vector<Polygon>();
	private VertexMap vertexMap = new VertexMap();
	private DataSet data;
	private CadastreImage img;
	public static final int INC = 4;
	public static final boolean DEBUG = true;

	public void run() {
		LatLon startLatlon = Main.map.mapView.getLatLon(0, Main.map.mapView
				.getHeight() - 1);
		LatLon endLatlon = Main.map.mapView.getLatLon(Main.map.mapView
				.getWidth() - 1, 0);

		try {
			img = new CadastreImage(startLatlon, endLatlon);
			Tile tile = img.changeToNextTile();
			Polygon polygon;
			Bound<Integer> bound;
			long totalCounts = (long) CadastreImage.getHeight()
					* (long) CadastreImage.getWidth() / (INC * INC);
			long counter = 0;
			do {
				tile.load();
				System.out.println("--------------------------- Tile " + tile.getXtile() + ", " + tile.getYtile() + "---------------------------");
				if (tile.isEmpty()) {
					counter += tile.getNumPixels() / (INC * INC);
					CadastrePlugin.progressFrame.setProgress((int) ((counter * 100) / totalCounts));
				}
				else {
					bound = tile.getBound();
					int leftSteps;
					int tileBuildings = 0;
					System.out.println("Not empty, scanning... ");
					// Detect buildings
					for (int y = INC + bound.top; y < bound.bottom; y += INC) {
						CadastrePlugin.progressFrame.setProgress((int) ((counter * 100) / totalCounts));
						for (int x = INC + bound.left; x < bound.right; x += INC) {
							counter++;
							Pixel p = new Pixel(x, y);
							
							if (p.isBuilding()) {
								leftSteps = 0;
								while (!p.left().isBorder()) {
									p = p.left();
									if (++leftSteps > INC) {
										p = null;
										break;
									}
								}
								if (p == null)
									continue;
								try {
									polygon = new Polygon(p.left(), false);
									polygons.add(polygon);
									tileBuildings++;
									// Load vertexMap with vertexes found
									for (PolyPixel vertex : polygon.getVertexes())
										vertexMap.put(vertex);
									/*for (Polygon hole : polygon.getHoles()) {
										for (PolyPixel vertex : hole.getVertexes()) {
											vertex.setRGB(Pixel.VERTEX);
										}
									}*/
								} catch (NotPolygon e) {
								} catch (NotClosedPolygon e) {
									System.out.println(e.getMessage());
								} catch (java.lang.StackOverflowError e) {
									System.out.println("Overflow Error at starting polygon pixel: "	+ p.left().x() + ", " + p.left().y());
								}
							}
						}
					}
					System.out.println(tileBuildings + " buidings found");
				}
				tile = img.changeToNextTile();
			} while (tile != null);
		} catch (CriticalException e) {
			CadastrePlugin.progressFrame.dispose();
			JOptionPane.showMessageDialog(null, e.getMessage(), "Error",
					JOptionPane.ERROR_MESSAGE);
			return;
		}
		System.out.println(Tile.downloads + " tiles downloaded from " + CadastreImage.getXtiles() * CadastreImage.getYtiles() + " tiles, " + Tile.skipped + " skipped tiles");
		System.out.println(Polygon.buildings + " total buildings found!");

		System.out.print("Joining near vertexes... ");
		// Join near vertexes
		
		for (Polygon pol : polygons)
			for (PolyPixel vertex : pol.getVertexes())
				if (!vertex.isFinal())
					joinNearVertexes(vertex);
		System.out.println("done");

		System.out.print("Cleaning overlapped vertexes... ");
		for (Polygon pol : polygons) {
			int numVertexes = pol.getVertexes().size();
			for (int vnum = 0; vnum < numVertexes; vnum++) {
				int afterIndex = (vnum + 1) % numVertexes;
				PolyPixel after = pol.getVertexes().get(afterIndex);
				PolyPixel current = pol.getVertexes().get(vnum);

				if (current.equal(after) || current.distance(after) < 3) {
					pol.getVertexes().remove(vnum);
					current.setVertex(false);
					numVertexes--;
					vnum--;
					continue;
				}
			}
		}
		System.out.println("done");

		System.out.print("Joining near polygons... ");
		// Join near polygons
		PolyPixel start, current, nearVertex, nextVertex, lastVertex, newVertex, curMove, nearerPixel;
		int lastVertexIndex, minDistance, curDistance;
		for (Polygon pol : polygons) {
			if (pol.getVertexes().size() < 3)
				continue;
			start = pol.getVertexes().firstElement();
			if (start.x() == 241 && start.y() == 617)
				System.out.print("");
			lastVertex = start;
			current = start;
			do
				current = current.getNext();
			while (current.equal(start));
			nextVertex = pol.getVertexes().get(1);
			lastVertexIndex = 0;

			// while (!current.equal(start) && !(!lastVertex.equal(start) &&
			// current.distance(start) < 6)) {
			while (!current.equal(start)) {
				if (current.x() == 7796 && current.y() == 2895)
					System.out.print("");
				if (current.equal(lastVertex)) {
					current = current.getNext();
					continue;
				}
				if (current.getVertex()) {
					lastVertex = current;
					lastVertexIndex++;
					if (pol.getVertexes().size() <= (lastVertexIndex + 1)) { // If vertex next is first vertex
						lastVertexIndex = 0;
						nextVertex = pol.getVertexes().get(0);
					} else
						nextVertex = pol.getVertexes().get(lastVertexIndex + 1);

				} else if (current.distance(lastVertex) > 0
						&& current.distance(nextVertex) > 0) {
					nearVertex = getNearVertex(current, pol.getVertexes());
					if (nearVertex != null) { // Found a vertex from a near
												// building
						curMove = current;
						nearerPixel = current;
						minDistance = Integer.MAX_VALUE;
						while (curMove.distance(nextVertex) > 3) {
							curDistance = curMove.distance(nearVertex);
							if (curDistance < minDistance) {
								nearerPixel = curMove;
								minDistance = curDistance;
							}
							curMove = curMove.getNext();
						}
						if (minDistance < 3) {
							// Create new vertex from data of near vertex found
							newVertex = new PolyPixel(nearVertex);
							newVertex.setVertex(true);
							newVertex.setId(nearVertex.getId());

							// New links for new vertex
							newVertex.setPrev(nearerPixel);
							newVertex.setNext(nearerPixel.getNext());
							nearerPixel.getNext().setPrev(newVertex);
							nearerPixel.setNext(newVertex);

							if (lastVertexIndex == 0
									&& !lastVertex.equal(start)) // Append
																	// vertex
								pol.getVertexes().add(newVertex);
							else
								pol.getVertexes().add(++lastVertexIndex,
										newVertex); // Insert vertex
							current = newVertex.getNext();
							lastVertex = newVertex;
							continue;
						} else {
							current = nearerPixel.getNext();
							continue;
						}
					}
				}
				current = current.getNext();
			}
		}
		System.out.println("done");

		System.out.print("Loading data... ");
		data = new DataSet();
		Node n, hn;
		Way w, hw;
		Relation r;
		for (Polygon pol : polygons) {
			if (pol.getVertexes().size() < 3)
				continue;
			w = new Way();
			for (PolyPixel vertex : pol.getVertexes()) {
				n = vertexMap.getNode(vertex);
				if (n == null) {
					n = new Node();
					n.setCoor(new LatLon(vertex.getLat(), vertex.getLon()));
					data.addPrimitive(n);
					vertexMap.setNode(vertex, n);
				}
				w.addNode(n);
			}
			w.addNode(w.firstNode());
			w.put("building", "yes");
			w.put("source", "catastro");
			data.addPrimitive(w);

			if (pol.getHoles().size() > 0) {
				r = new Relation();
				r.addMember(new RelationMember("outer", w));
				r.put("type", "multipolygon");
				for (Polygon hole : pol.getHoles()) {
					hw = new Way();
					for (PolyPixel vertex : hole.getVertexes()) {
						hn = vertexMap.getNode(vertex);
						if (hn == null) {
							hn = new Node();
							hn.setCoor(new LatLon(vertex.getLat(), vertex
									.getLon()));
							data.addPrimitive(hn);
							vertexMap.setNode(vertex, hn);
						}
						hw.addNode(hn);
					}
					hw.addNode(hw.firstNode());
					data.addPrimitive(hw);
					r.addMember(new RelationMember("inner", hw));
				}
				data.addPrimitive(r);
			}
		}

		Layer layer = new OsmDataLayer(data, "Imported buildings", null);
		Main.map.mapView.addLayer(layer);

		System.out.println("done");
		CadastrePlugin.progressFrame.dispose();
		/*File outputfile = new File("0_0.png");
        try {
            ImageIO.write(img.tiles[0][0].tile, "png", outputfile);
        } catch (IOException e) {
        }*/
		/*
		 * if (debug) { File outputfile = new File("final.png"); try {
		 * ImageIO.write(img, "png", outputfile); } catch (IOException e) {
		 * e.printStackTrace(); } //url =
		 * "file:/home/marc/workspace/cadastre_es/final3.png"; //img =
		 * ImageIO.read(new URL(url)); }
		 */
	}

	private void joinNearVertexes(PolyPixel vertex) {
		Vector<PolyPixel> nearVertexes = getNearVertexes(vertex);
		Pixel finalVertex;
		int x = 0;
		int y = 0;

		if (nearVertexes.size() > 1) {
			for (PolyPixel nearVertex : nearVertexes) {
				x += nearVertex.x();
				y += nearVertex.y();
			}
			finalVertex = new Pixel(x / nearVertexes.size(), y
					/ nearVertexes.size());
			for (PolyPixel nearVertex : nearVertexes) {
				//nearVertex.setRGB(0xFF00FFFF);
				vertexMap.remove(nearVertex);
				nearVertex.copy(finalVertex);
				nearVertex.setFinal(true);
				vertexMap.put(nearVertex);
			}
			//finalVertex.setRGB(0xFF000000);
		}
		//else
		//	vertex.setRGB(Pixel.VERTEX);
	}

	private java.util.Vector<PolyPixel> getNearVertexes(PolyPixel v) {
		Vector<PolyPixel> nearVertexes = new Vector<PolyPixel>();
		Pixel cur;
		for (int dx = -3; dx < 4; dx++) {
			for (int dy = -3; dy < 4; dy++) {
				cur = new Pixel(v.x() + dx, v.y() + dy);
				if (cur.distance(v) > 4)
					continue;
				Vector<PolyPixel> nvs = vertexMap.get(cur);
				if (nvs == null)
					continue;
				for (PolyPixel nv : nvs) {
					if (!nv.isFinal())
						nearVertexes.add(nv);
				}
			}
		}
		return nearVertexes;
	}

	private PolyPixel getNearVertex(PolyPixel v, Vector<PolyPixel> vertexes) {
		Pixel cur;
		PolyPixel result = null;
		int distance = Integer.MAX_VALUE;
		int curDistance;
		for (int dx = -3; dx < 4; dx++) {
			for (int dy = -3; dy < 4; dy++) {
				cur = new Pixel(v.x() + dx, v.y() + dy);
				curDistance = cur.distance(v);
				if (curDistance > 4)
					continue;
				Vector<PolyPixel> nvs = vertexMap.get(cur);
				if (nvs != null && nvs.size() > 0
						&& nvs.firstElement().distance(v) < distance) {
					boolean same = false;
					for (PolyPixel vertex : vertexes) {
						if (vertex.distance(nvs.firstElement()) < 2) {
							same = true;
							break;
						}
					}
					if (!same) {
						result = nvs.firstElement();
						distance = curDistance;
					}
				}
			}
		}
		return result;
	}

	public DataSet getData() {
		return data;
	}
}
